/*  
*   This file is a part of Neutrino - a lightweight UI library accelerated with OpenGL
*   Copyright (C) 2023 bsolos
*
*   This program is free software: you can redistribute it and/or modify
*   it under the terms of the GNU General Public License as published by
*   the Free Software Foundation, either version 3 of the License, or
*   (at your option) any later version.
*
*   This program is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU General Public License for more details.
*
*   You should have received a copy of the GNU General Public License
*   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <neutrino.hpp>

using namespace neutrino;

surface::surface(int width, int height) 
    : width(width), height(height) {}


surface::~surface() {}


void surface::clear() {}


void surface::set_color(color c) {
    glColor4d(c.r, c.g, c.b, c.a);
}


void surface::draw_primitives(GLenum type, point points[], size_t count) {
    glBegin(type);
    send_points(points, count);
    glEnd();
}


void surface::draw_triangle(point p1, point p2, point p3) {
    point points[] = {p1, p2, p3};
    draw_primitives(GL_TRIANGLES, points, 3);
}


void surface::draw_line(point p1, point p2) {
    point points[] = {p1, p2};
    draw_primitives(GL_LINES, points, 2);
}


int surface::get_width() {
    return width;
}


int surface::get_height() {
    return height;
}


double surface::map_x(int x) {
    double scaled = ((double)x / width) * 2.0;
    return scaled - 1.0;
}


double surface::map_y(int y) {
    double scaled = ((double)y / height) * 2.0;
    return 1.0 - scaled;
}


void surface::send_points(point points[], size_t count) {
    for (size_t i = 0; i < count; i++) {
        double target_x = map_x(points[i].x);
        double target_y = map_y(points[i].y);
        glVertex2d(target_x, target_y);
    }
}


subsurface surface::create_subsurface(int x, int y, int width, int height) {
    return subsurface(this, x, y, width, height);
}


buffer_surface::buffer_surface(unsigned int fbo, GLenum buffer, int width, int height)
    : surface(width, height), buffer(buffer), fbo(fbo) {}


void buffer_surface::clear() {
    bind_buffer();
    glClear(GL_COLOR_BUFFER_BIT);
}


void buffer_surface::draw_primitives(GLenum type, point points[], size_t count) {
    bind_buffer();
    surface::draw_primitives(type, points, count);
}


void buffer_surface::bind_buffer() {
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glDrawBuffer(buffer);
}


window_surface::window_surface(render_context *ctx, int width, int height) 
    : buffer_surface(0, GL_BACK, width, height), ctx(ctx) {}


void window_surface::draw_primitives(GLenum type, point points[], size_t count) {
    buffer_surface::draw_primitives(type, points, count);
    ctx->mark_for_present();
}


subsurface::subsurface(surface *parent, int x, int y, int width, int height) 
    : surface(width, height), parent(parent), pos_x(x), pos_y(y) {}


void subsurface::clear() {
    set_color({0.0f, 0.0f, 0.0f, 0.0f});

    point points[] = {
        {0, 0}, 
        {width, 0}, 
        {0, height},
        {width, height}
    };
    draw_primitives(GL_TRIANGLE_STRIP, points, 4);
}


void subsurface::draw_primitives(GLenum type, point points[], size_t count) {
    point *new_points = new point[count];
    for (size_t i = 0; i < count; i++) {
        new_points[i] = {points[i].x+pos_x, points[i].y+pos_y};
    }

    parent->draw_primitives(type, new_points, count);

    delete[] new_points;
}


subsurface subsurface::create_subsurface(int x, int y, int width, int height) {
    return subsurface(parent, pos_x+x, pos_y+y, width, height);
}


double subsurface::map_x(int x) {
    return parent->map_x(pos_x+x);
}


double subsurface::map_y(int y) {
    return parent->map_y(pos_y+y);
}